define([], function () {

	"use strict";

	var arrayProto = Array.prototype, slice = arrayProto.slice,
		obj = Object, objProto = Object.prototype, toString = objProto.toString, assign = obj.assign,
		strProto = String.prototype, upper = strProto.toUpperCase,
		hasStorage = true;

	if (typeof Storage === 'undefined' || Storage === null)
		hasStorage = false;

	var Base64 = {

		// private property
		_keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

		// public method for encoding
		encode: function (input) {
			var output = "";
			var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
			var i = 0;

			input = Base64._utf8_encode(input);

			while (i < input.length) {

				chr1 = input.charCodeAt(i++);
				chr2 = input.charCodeAt(i++);
				chr3 = input.charCodeAt(i++);

				enc1 = chr1 >> 2;
				enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
				enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
				enc4 = chr3 & 63;

				if (isNaN(chr2)) {
					enc3 = enc4 = 64;
				} else if (isNaN(chr3)) {
					enc4 = 64;
				}

				output = output +
					this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
					this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

			}

			return output;
		},

		// public method for decoding
		decode: function (input) {
			var output = "";
			var chr1, chr2, chr3;
			var enc1, enc2, enc3, enc4;
			var i = 0;

			input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

			while (i < input.length) {

				enc1 = this._keyStr.indexOf(input.charAt(i++));
				enc2 = this._keyStr.indexOf(input.charAt(i++));
				enc3 = this._keyStr.indexOf(input.charAt(i++));
				enc4 = this._keyStr.indexOf(input.charAt(i++));

				chr1 = (enc1 << 2) | (enc2 >> 4);
				chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
				chr3 = ((enc3 & 3) << 6) | enc4;

				output = output + String.fromCharCode(chr1);

				if (enc3 != 64) {
					output = output + String.fromCharCode(chr2);
				}
				if (enc4 != 64) {
					output = output + String.fromCharCode(chr3);
				}

			}

			output = Base64._utf8_decode(output);

			return output;

		},

		// private method for UTF-8 encoding
		_utf8_encode: function (string) {
			string = string.replace(/\r\n/g, "\n");
			var utftext = "";

			for (var n = 0; n < string.length; n++) {

				var c = string.charCodeAt(n);

				if (c < 128) {
					utftext += String.fromCharCode(c);
				}
				else if ((c > 127) && (c < 2048)) {
					utftext += String.fromCharCode((c >> 6) | 192);
					utftext += String.fromCharCode((c & 63) | 128);
				}
				else {
					utftext += String.fromCharCode((c >> 12) | 224);
					utftext += String.fromCharCode(((c >> 6) & 63) | 128);
					utftext += String.fromCharCode((c & 63) | 128);
				}

			}

			return utftext;
		},

		// private method for UTF-8 decoding
		_utf8_decode: function (utftext) {
			var string = "";
			var i = 0;
			var c = 0, c1 = 0, c2 = 0, c3 = 0;

			while (i < utftext.length) {

				c = utftext.charCodeAt(i);

				if (c < 128) {
					string += String.fromCharCode(c);
					i++;
				}
				else if ((c > 191) && (c < 224)) {
					c2 = utftext.charCodeAt(i + 1);
					string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
					i += 2;
				}
				else {
					c2 = utftext.charCodeAt(i + 1);
					c3 = utftext.charCodeAt(i + 2);
					string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
					i += 3;
				}

			}

			return string;
		}

	};

	function callFunc(bind, fn) {
		var args = [];
		if (typeof bind === 'function') {
			args = slice.call(arguments, 1);
			fn = bind;
			bind = null;
		}
		else {
			bind = bind || null;
			if (typeof fn !== 'function')
				return false;
			args = slice.call(arguments, 2);
		}
		fn.apply(bind, args);
		return true;
	}

	function round(value, precision) {
		if (isNaN(value))
			value = 0.00;
		if (typeof precision === 'undefined' || isNaN(precision))
			precision = 0;
		precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
		return Math.round(value * precision) / precision;
	}

	var KB = 1024, MB = 1024 * 1024, GB = 1024 * 1024 * 1024, TB = 1024 * 1024 * 1024 * 1024;

	function adjustSize(size, precision) {
		if (isNaN(precision) || precision <= 0)
			precision = 6;
		if (size < KB)
			return round(size, precision) + ' B';
		else if (size < MB)
			return round(size / KB, precision) + ' KB';
		else if (size < GB)
			return round(size / MB, precision) + ' MB';
		else if (size < TB)
			return round(size / GB, precision) + ' GB';
		else
			return round(size / TB, precision) + ' TB';
	}

	var any = false
		, anyInit = false, anyDebug = false, anyBuild = false
		, anyTimeout = 10
		, anyCache = hasStorage ? false : {}, anyCacheKey = 'require-any-cache', anyCacheMax = 10 * MB
		;

	var reTrim = /^[\s\t\r\n]+|[\s\t\r\n]+$/g;

	function isString(obj) {
		return toString.call(obj) === '[object String]';
	}

	function isArray(obj) {
		return toString.call(obj) === '[object Array]';
	}

	function isUndefined(obj, then) {
		return typeof obj === 'undefined' ? then : obj;
	}

	function isSet(obj, then) {
		return typeof obj === 'undefined' || obj === null ? then : obj;
	}

	function jsonDecode(value, then) {
		var data;
		try {
			data = JSON.parse(value);
		}
		catch (error) {
			data = then;
			if (anyDebug)
				console.error('JSON.parse error', error);
		}
		return data;
	}

	function jsonEncode(value) {
		return JSON.stringify(value);
	}

	function initAny(config) {
		if (anyInit)
			return false;
		config = config || {};
		anyInit = true;
		any = assign({}, config.any || {});
		anyDebug = !!any.debug;
		anyBuild = !!any.build; // 和rjs的配置区分开，不要混淆使用
		if (!isNaN(any.timeout) && any.timeout > 0)
			anyTimeout = any.timeout;
		if (isString(any.cacheKey) && any.cacheKey !== '')
			anyCache = any.cacheKey;
		return true;
	}

	function initLocalCache() {
		if (anyCache === false) {
			var text = localStorage.getItem(anyCacheKey);
			if (isSet(text)) {
				if (anyDebug)
					console.info('localStorage size', adjustSize(anyCacheMax - text.length));
				anyCache = jsonDecode(text, {});
			}
			else {
				anyCache = {};
			}
		}
		return anyCache;
	}

	var xhrMethods = {GET: 'GET', POST: 'POST', DELETE: 'DELETE', PUT: 'PUT'}
		, xhrDefaultMethod = 'GET';

	function xhrFilterMethod(method) {
		return (method && xhrMethods[upper.call(method)]) || xhrDefaultMethod;
	}

	function initXhrOptions(url, options) {
		options = options || {};

		options.url = url;
		options.method = xhrFilterMethod(options.method);
		options.async = isUndefined(options.async, true);

		return options;
	}

	function initXhr(url, options) {
		if (typeof XMLHttpRequest === 'undefined' || XMLHttpRequest === null)
			throw new Error('Your browser unsupported XMLHttpRequest!');
		options = initXhrOptions(url, options);
		var xhr = new XMLHttpRequest()
			, isError = false, isAbort = false
			;
		xhr.open(options.method, options.url, options.async);
		xhr.onerror = function (event) {
			isError = true;
			callFunc(xhr, options.error, new Error('Network Error', url));
		};
		xhr.onabort = function (event) {
			isAbort = true;
			callFunc(xhr, options.abort);
		};
		xhr.onload = function (event) {
			if (!isAbort && !isError) {
				callFunc(xhr, options.done);
			}
		};
		xhr.onreadystatechange = function (event) {
			if (!isAbort && !isError) {
				if (xhr.status > 399 && xhr.status < 600) {
					isError = true;
					callFunc(xhr, options.error, new Error('HTTP status: ' + xhr.status, url));
				}
				else {
					switch (xhr.readyState) {
						case 2 :
							callFunc(xhr, options.headers);
							break;
						case 3 :
							callFunc(xhr, options.loading);
							break;
					}
				}
			}
		};
		xhr.send(null);
		return xhr;
	}

	return {
		Base64: Base64,
		isSet: isSet,
		isString: isString,
		isArray: isArray,
		isUndefined: isUndefined,
		jsonDecode: jsonDecode,
		jsonEncode: jsonEncode,
		call: callFunc,
		round: round,
		adjustSize: adjustSize,
		xhr: initXhr,
		getFileExt: function (name) {
			var match = name.match(/\.([a-z0-9-_]+)$/i);
			if (match)
				return match[1].toLowerCase();
			return false;
		},
		removeExt: function (name, ext) {
			if (ext != null && ext.length > 0) {
				var regex = new RegExp('.' + ext + '$', 'i');
				return name.replace(regex, '');
			}
			return name;
		},
		getRealExt: function (ext) {
			if (!anyInit)
				throw new Error('require-any is not init!');
			return (any.ext || {})[ext] || ext;
		},
		getExtHandle: function (ext) {
			return (any.plugins || {})[this.getRealExt(ext)] || false;
		},
		getExtOptions: function (ext) {
			console.log(1, ext);
			// 加载后缀的选项时，优先加载当前的选项。
			var options = any.options || {};
			if (options[ext])
				return options[ext];
			ext = this.getRealExt(ext);
			console.log(1, ext);
			return options[ext] || {};
		},
		isBuild: function () {
			return anyBuild;
		},
		isDebug: function () {
			return anyDebug;
		},
		verifyCache: function (cache) {
			if (cache && cache.date && cache.date > 0 && cache.length && cache.length > 0 && cache.text && cache.text != null)
				return cache;
			return false;
		},
		getCache: function (name) {
			return this.verifyCache(initLocalCache()[name]);
		},
		updateCache: function (name, cache) {
			if (this.verifyCache(cache) !== false) {
				initLocalCache();
				anyCache[name] = cache;
				if (hasStorage) {
					var data = jsonEncode(anyCache);
					localStorage.setItem(anyCacheKey, data);
					if (anyDebug)
						console.log('localStorage size', adjustSize(anyCacheMax - data.length));
				}
			}
			return cache;
		},
		hasCache: function (name) {
			return this.getCache(name) !== false;
		},
		compareCache: function (name, date, length) {
			var cache = this.getCache(name);
			if (cache === false || cache.date < date || cache.length !== length)
				return false;
			return cache;
		},
		load: function (name, req, load, config) {
			initAny(config);
			var ext = this.getFileExt(name);
			if (this.isBuild() || ext === false) {
				req([this.removeExt(name, ext)], function (obj) {
					load(obj);
				});
			}
			else {
				var handle = this.getExtHandle(ext)
					, error = null;
				var onHandle = function (handler) {
					handler = handler || {};
					if (callFunc(handler.handle, name, req, load, config) === false)
						load.error(new Error('Invalid handler with extension "' + match[1].toLowerCase() + '"'));
				};

				switch (toString.call(handle)) {
					case '[object Function]' :
						onHandle({handle: handle});
						break;
					case '[object String]' :
						req([handle], onHandle);
						break;
					case '[object Array]' :
						req(handle, onHandle);
						break;
					case '[object Object]' :
						onHandle(handle);
						break;
					default :
						error = new Error('Unknown handle with extension "' + ext + '"');
				}
				if (error != null)
					throw error;
			}
		}
	};
});
